﻿//----------------------------------------------------------------
// Copyright (c) Microsoft Corporation.  All rights reserved.
// System.ServiceModel.Internals\System\Runtime\AsyncWaitHandle.cs
//----------------------------------------------------------------
#if DESKTOPCLR
using System;
using System.Collections.Generic;
using System.Security;
using System.Threading;
using CuteAnt.Diagnostics;

namespace CuteAnt.Runtime
{
  [Fx.Tag.SynchronizationPrimitive(Fx.Tag.BlocksUsing.MonitorWait, SupportsAsync = true, ReleaseMethod = "Set")]
  public class AsyncWaitHandle
  {
    static Action<object> timerCompleteCallback;

    List<AsyncWaiter> asyncWaiters;
    bool isSignaled;
    EventResetMode resetMode;

    [Fx.Tag.SynchronizationObject(Kind = Fx.Tag.SynchronizationKind.MonitorWait)]
    object syncObject;

    int syncWaiterCount;

    public AsyncWaitHandle()
        : this(EventResetMode.AutoReset)
    {
    }

    public AsyncWaitHandle(EventResetMode resetMode)
    {
      this.resetMode = resetMode;
      this.syncObject = new object();
    }

    public bool WaitAsync(Action<object, TimeoutException> callback, object state, in TimeSpan timeout)
    {
      if (!this.isSignaled || (this.isSignaled && this.resetMode == EventResetMode.AutoReset))
      {
        lock (syncObject)
        {
          if (this.isSignaled && this.resetMode == EventResetMode.AutoReset)
          {
            this.isSignaled = false;
          }
          else if (!this.isSignaled)
          {
            AsyncWaiter waiter = new AsyncWaiter(this, callback, state);

            if (this.asyncWaiters == null)
            {
              this.asyncWaiters = new List<AsyncWaiter>();
            }

            this.asyncWaiters.Add(waiter);

            if (timeout != TimeSpan.MaxValue)
            {
              if (timerCompleteCallback == null)
              {
                timerCompleteCallback = new Action<object>(OnTimerComplete);
              }
              waiter.SetTimer(timerCompleteCallback, waiter, timeout);
            }
            return false;
          }
        }
      }

      return true;
    }

    static void OnTimerComplete(object state)
    {
      AsyncWaiter waiter = (AsyncWaiter)state;
      AsyncWaitHandle thisPtr = waiter.Parent;
      bool callWaiter = false;

      lock (thisPtr.syncObject)
      {
        // If still in the waiting list (that means it hasn't been signaled)
        if (thisPtr.asyncWaiters != null && thisPtr.asyncWaiters.Remove(waiter))
        {
          waiter.TimedOut = true;
          callWaiter = true;
        }
      }

      waiter.CancelTimer();

      if (callWaiter)
      {
        waiter.Call();
      }
    }

    [Fx.Tag.Blocking]
    public bool Wait(in TimeSpan timeout)
    {
      if (!this.isSignaled || (this.isSignaled && this.resetMode == EventResetMode.AutoReset))
      {
        lock (syncObject)
        {
          if (this.isSignaled && this.resetMode == EventResetMode.AutoReset)
          {
            this.isSignaled = false;
          }
          else if (!this.isSignaled)
          {
            bool decrementRequired = false;

            try
            {
              try
              {
              }
              finally
              {
                this.syncWaiterCount++;
                decrementRequired = true;
              }

              if (timeout == TimeSpan.MaxValue)
              {
                if (!Monitor.Wait(syncObject, Timeout.Infinite))
                {
                  return false;
                }
              }
              else
              {
                if (!Monitor.Wait(syncObject, timeout))
                {
                  return false;
                }
              }
            }
            finally
            {
              if (decrementRequired)
              {
                this.syncWaiterCount--;
              }
            }
          }
        }
      }

      return true;
    }

    public void Set()
    {
      List<AsyncWaiter> toCallList = null;
      AsyncWaiter toCall = null;

      if (!this.isSignaled)
      {
        lock (syncObject)
        {
          if (!this.isSignaled)
          {
            if (this.resetMode == EventResetMode.ManualReset)
            {
              this.isSignaled = true;
              Monitor.PulseAll(syncObject);
              toCallList = this.asyncWaiters;
              this.asyncWaiters = null;
            }
            else
            {
              if (this.syncWaiterCount > 0)
              {
                Monitor.Pulse(syncObject);
              }
              else if (this.asyncWaiters != null && this.asyncWaiters.Count > 0)
              {
                toCall = this.asyncWaiters[0];
                this.asyncWaiters.RemoveAt(0);
              }
              else
              {
                this.isSignaled = true;
              }
            }
          }
        }
      }

      if (toCallList != null)
      {
        foreach (AsyncWaiter waiter in toCallList)
        {
          waiter.CancelTimer();
          waiter.Call();
        }
      }

      if (toCall != null)
      {
        toCall.CancelTimer();
        toCall.Call();
      }
    }

    public void Reset()
    {
      // Doesn't matter if this changes during processing of another method
      this.isSignaled = false;
    }

    class AsyncWaiter : ActionItem
    {
      [Fx.Tag.SecurityNote(Critical = "Store the delegate to be invoked")]
      [SecurityCritical]
      Action<object, TimeoutException> callback;
      [Fx.Tag.SecurityNote(Critical = "Stores the state object to be passed to the callback")]
      [SecurityCritical]
      object state;
      IOThreadTimer timer;
      TimeSpan originalTimeout;

      [Fx.Tag.SecurityNote(Critical = "Access critical members", Safe = "Doesn't leak information")]
      [SecuritySafeCritical]
      public AsyncWaiter(AsyncWaitHandle parent, Action<object, TimeoutException> callback, object state)
      {
        this.Parent = parent;
        this.callback = callback;
        this.state = state;
      }

      public AsyncWaitHandle Parent
      {
        get;
        private set;
      }

      public bool TimedOut
      {
        get;
        set;
      }

      [Fx.Tag.SecurityNote(Critical = "Calls into critical method Schedule", Safe = "Invokes the given delegate under the current context")]
      [SecuritySafeCritical]
      public void Call()
      {
        Schedule();
      }

      [Fx.Tag.SecurityNote(Critical = "Overriding an inherited critical method, access critical members")]
      [SecurityCritical]
      protected override void Invoke()
      {
        this.callback(this.state,
            this.TimedOut ? new TimeoutException(InternalSR.TimeoutOnOperation(this.originalTimeout)) : null);
      }

      public void SetTimer(Action<object> callback, object state, in TimeSpan timeout)
      {
        if (this.timer != null)
        {
          throw Fx.Exception.AsError(new InvalidOperationException(InternalSR.MustCancelOldTimer));
        }

        this.originalTimeout = timeout;
        this.timer = new IOThreadTimer(callback, state, false);

        this.timer.Set(timeout);
      }

      public void CancelTimer()
      {
        if (this.timer != null)
        {
          this.timer.Cancel();
          this.timer = null;
        }
      }
    }
  }
}
#endif
